package fr.emse.tatiana.replayable;


import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.MultiPageEditorPart;

import fr.emse.tatiana.display.BadDisplayFile;
import fr.emse.tatiana.remotecontrol.RemoteControl;
import fr.emse.tatiana.replayable.analysis.AnalysisEvent;
import fr.emse.tatiana.replayable.analysis.IAnalysis;
import fr.emse.tatiana.replayable.analysis.IAnalysisEditor;
import fr.emse.tatiana.replayable.tabulardisplay.IReplayableEditor;

public abstract class AbstractReplayableEditor extends MultiPageEditorPart implements ISelectionChangedListener, IReplayableEditor {

	protected List<IAnalysis> analyses;

	public AbstractReplayableEditor() {
		analyses = new Vector<IAnalysis>();
	}
	@Override
	public void doSave(IProgressMonitor monitor) {
		//
		if (!getReplayableSaver().hasFile()) {
			doSaveAs();
		} else {
			getReplayableSaver().doSave();
			firePropertyChange(PROP_DIRTY);
		}
	}

	
	
	public abstract IReplayableSaver getReplayableSaver();

	@Override
	public void doSaveAs() {
		InputDialog dialog = new InputDialog(getContainer().getShell(), 
				"Save replayable as...", 
				"Name of new replayable:", null, null); 
		while(true){
			if (dialog.open() != Window.OK){
				return;
			}
			else{
				if(dialog.getValue().length()!=0){
					getReplayableSaver().doSave(dialog.getValue());
					setPartName(getReplayableSaver().getName());
					firePropertyChange(PROP_DIRTY);
					firePropertyChange(PROP_TITLE);
					return;
				}
				else {
					
				}
			}
		}
	}

	@Override
	public boolean isSaveAsAllowed() {
		return true;
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#analysisEventOccurred(fr.emse.tatiana.replayable.analysis.AnalysisEvent)
	 */
	public void analysisEventOccurred(AnalysisEvent evt) {
		switch(evt.getType()) {
		case AnalysisEvent.LINK_CHANGE:
		case AnalysisEvent.ELEMENT_CHANGE:
			if (getModel()!=null) {
				Object[] rows = getModel().getAnalysedEvents(evt);
				updateViewer(rows);
			}
			break;
		case AnalysisEvent.TITLE_CHANGE:
			if (getModel()!=null) {
				int col = getModel().getFacetIdFor((IAnalysis)evt.getSource());
				updateTitle(col,getModel().getFacetName(col));
			}
		}

	}

	protected abstract void updateViewer(Object[] rows);

	protected abstract void updateTitle(int facet, String name);

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#removeAnalysis(fr.emse.tatiana.replayable.analysis.Analysis)
	 */
	public void removeAnalysis(IAnalysis a) {
		if (analyses.contains(a)) {
			analyses.remove(a);
			a.removeAnalysisEventListener(this);
		}
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#addAnalysis(fr.emse.tatiana.replayable.analysis.Analysis)
	 */
	public void addAnalysis(IAnalysis a) {
		analyses.add(a);
		a.addAnalysisEventListener(this);
	}

	/**
	 * @return the model that is currently viewed in the viewer
	 */
	public abstract IReplayableModel getModel();

	@Override
	public void dispose() {
		getReplayableSaver().close();
		for (IAnalysis a: analyses) {
			a.removeAnalysisEventListener(this);
		}
		if (remote!=null) {
			remote.removeReplayer(this);
		}
		super.dispose();
	}

	private RemoteControl remote;

	protected RemoteControl getRemote() {
		return remote;
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#setRemote(fr.emse.tatiana.replayable.ContinuousReplayable)
	 */
	public void setRemote(RemoteControl remote) {
		this.remote = remote;
		this.startSynchronisation();
	}

	protected abstract void startSynchronisation();

	protected abstract void endSynchronisation();

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#getEvents()
	 */
	public List<Long> getEvents() {
		if (getModel()!=null)
			return getModel().getEventTimestamps();
		else 
			return new ArrayList<Long>();
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#end()
	 */
	public void end() {
		endSynchronisation();
		remote = null; // so that we don't try to repeatedly end a remote
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#goTo(fr.emse.tatiana.replayable.SynchronisationEvent)
	 */
	public void goTo(SynchronisationEvent event) {

		// check we are not source

		if (this == event.getSource()) {
			return;
		}
		Long time = event.getNow();

		// first set selection to now (if there is one)

		if (!event.isIdsynch()) {
			Object row = getModel().lastEventBefore(time);
			StructuredSelection now = StructuredSelection.EMPTY;
			if (row!=null) {
				now = new StructuredSelection(row);
			}
			selectEvents(now, true, false); // scroll to now
			List<Object> elems = getModel().currentEvents(event);
			StructuredSelection sel = StructuredSelection.EMPTY;
			if (elems.size() > 0) sel = new StructuredSelection(elems);
			selectEvents(sel, false, false); // select the current ones
		} else {
			if (event.getAnchors().size() > 0) {
				List<Object> elems = getModel().anchoredEvents(event);
				StructuredSelection sel = StructuredSelection.EMPTY;
				if (elems.size() > 0) {
					StructuredSelection firstrow = new StructuredSelection(elems.get(0));
					selectEvents(firstrow, true, false);
					sel = new StructuredSelection(elems);
				}
				selectEvents(sel, false, false);
			}
		}



	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#mark(long)
	 */
	public void mark(long time) {

	}

	protected abstract void selectEvents (final StructuredSelection selection, final boolean scroll, final boolean propagate);


	@Override
	public void init(IEditorSite site, IEditorInput input)
	throws PartInitException {

		setPartName(input.getName());

		super.init(site,input);

		IReplayableInput in = null;
		if (input instanceof IReplayableInput) {
			in = (IReplayableInput) input;
		} else {
			throw new PartInitException("not an IReplayableInput");
		}

		try {
			in.init();
		} catch (BadDisplayFile e) {
			throw new PartInitException(e.getMessage());
		}


		IEditorReference[] editors = this.getSite().getWorkbenchWindow().getActivePage().getEditorReferences();
		if (editors!=null) {
			for (int i=0; i<editors.length; i++) {
				IEditorPart ed = editors[i].getEditor(false);
				if (ed instanceof IAnalysisEditor) {
					addAnalysis(((IAnalysisEditor) ed).getAnalysis());
				}
			}
		}

	}

	@Override
	public boolean isDirty() {
		return !getReplayableSaver().isSaved();
	}

	protected abstract Object getEventFromSelection(Object o);

	/* (non-Javadoc)
	 * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
	 */
	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.IArtefactEditor#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
	 */
	public void selectionChanged(SelectionChangedEvent event) {
		StructuredSelection sel = (StructuredSelection)event.getSelection();
		if (sel.isEmpty()) return;
		List<Object> sellist = sel.toList();
		try {
			long first = getModel().getBeginTime(getEventFromSelection(sel.getFirstElement()));
			long last = getModel().getLastTime(getEventFromSelection(sel.getFirstElement()));
			for (Object o: sellist) {
				Object e = getEventFromSelection(o);
				if (getModel().getBeginTime(e)<first) first = getModel().getBeginTime(e);
				if (getModel().getLastTime(e)>last) last = getModel().getLastTime(e);
			}
			System.err.println("RTD to: "+first);
			SynchronisationEvent se = new SynchronisationEvent(this,first, first, last-first);
			for (Object o : sellist) {
				se.addAnchor(getModel().getAnchor(getEventFromSelection(o)));
			}
			getRemote().goTo(se);
		} catch (ClassCastException cce) {
			// getEventFromSelection was called on an element that doesn't work
		}

	}


}
